﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using StuffNET.Math;

namespace StuffNET.Physics
{
    public class CollisionDetection
    {
        public delegate void CollisionDetectionDelegate(Coordinate[] collisionPoints, ICollidable c1, ICollidable c2);

        /// <summary>
        /// Gets invoked when a collison was detected
        /// </summary>
        public event CollisionDetectionDelegate CollisionDetected;

        /// <summary>
        /// Gets all ICollidalbe objects that are currently considered
        /// </summary>
        public ICollidable[] CollidableObjects { get { return objects.ToArray(); } }

        /// <summary>
        /// Gets all locations of currently occuring collisions 
        /// </summary>
        [Obsolete("CollisionLocations is very slow, use \'GetCollisions\' instead")]
        public Coordinate[] CollisionLocations
        {
            get
            {
                List<Coordinate> coords = new List<Coordinate>();
                for (int i = 0; i < currentCollisions.Count; i++)
                {
                    List<Collision> colls = currentCollisions.ElementAt(i).Value;
                    for (int j = 0; j < colls.Count; j++)
                    {
                            Coordinate[] tempCoords = currentCollisions.ElementAt(i).Value[j].Coordinates;
                        for (int k = 0; k < tempCoords.Length; k++)
                        {
                            if (!coords.Contains(tempCoords[k]))
                            {
                                coords.Add(tempCoords[k]);
                            }
                        }
                    }
                }
                return coords.ToArray();
            }
        }

        private Thread execThread;
        private bool run = false, pause = false;
        private List<ICollidable> objects = new List<ICollidable>();
        private Dictionary<ICollidable, List<Collision>> currentCollisions = new Dictionary<ICollidable, List<Collision>>();

        /// <summary>
        /// Creates a new instance of the CollisionDetection class
        /// </summary>
        public CollisionDetection()
        {
        }

        /// <summary>
        /// Adds an object to the collision-detection-engine
        /// </summary>
        /// <param name="collisionObject">An object that dimplements the ICollidable interface</param>
        public void AddObject(ICollidable collisionObject)
        {
            objects.Add(collisionObject);
            currentCollisions.Add(collisionObject, new List<Collision>());
        }

        /// <summary>
        /// Starts the engine
        /// </summary>
        public void Start()
        {
            run = true;
            execThread = new Thread(Run);
            execThread.IsBackground = true;
            execThread.Name = "Collision Detection Thread";
            execThread.Priority = ThreadPriority.BelowNormal;
            execThread.Start();
        }

        /// <summary>
        /// Stops the engine
        /// </summary>
        public void Stop()
        {
            run = false;
        }

        /// <summary>
        /// Pauses the engine
        /// </summary>
        public void Pause()
        {
            pause = true;
        }

        /// <summary>
        /// Resumes the engine
        /// </summary>
        public void Resume()
        {
            pause = false;
        }
        private void Run()
        {
            while (run)
            {
                while (pause) ;
                lock (objects)
                {
                    for (int i = 0; i < objects.Count; i++)
                    {
                        List<Collision> colls = new List<Collision>();
                        for (int j = 0; j < objects.Count; j++)
                        {
                            if (j == i)
                                continue;
                            colls.Add(new Collision(Collisions(objects[i], objects[j]), objects[j]));
                        }
                        for (int j = 0; j < colls.Count; j++)
                        {
                            for (int k = 0; k < currentCollisions[objects[i]].Count; k++)
                            {
                                if (currentCollisions[objects[i]][k] != colls[j])
                                {
                                    if (CollisionDetected != null)
                                        CollisionDetected(colls[j].Coordinates, objects[i], colls[j].Involved);
                                }
                            }
                        }
                        currentCollisions[objects[i]] = colls;
                    }
                }
            }

        }

        /// <summary>
        /// Get all collisions the given ICollidable object is involved in
        /// </summary>
        /// <param name="obj">The ICollidable object</param>
        /// <returns>An array of collisions</returns>
        public Collision[] GetCollisions(ICollidable obj)
        {
            return currentCollisions[obj].ToArray();
        }

        /// <summary>
        /// Checks the collisionstate of a given object
        /// </summary>
        /// <param name="obj">The object which should be checked</param>
        /// <returns>Returns the Coordniate of the intersection</returns>
        public bool HasCollision(ICollidable obj)
        {
            return GetCollisions(obj).Length != 0;
        }

        private Coordinate[] Collisions(ICollidable c1, ICollidable c2)
        {
            List<Coordinate> collisions = new List<Coordinate>();

            CollisionLine[] col1 = GetCollisionLines(c1);
            CollisionLine[] col2 = GetCollisionLines(c2);

            for (int i = 0; i < col1.Length; i++)
            {
                for (int j = 0; j < col2.Length; j++)
                {
                    Coordinate c = col1[i].IntersectsWidth(col2[j]);
                    if (c != null && c != Coordinate.Infinity)
                    {
                        if (!collisions.Contains(c))
                            collisions.Add(c);
                    }
                }
            }

            return collisions.ToArray();
        }
        private CollisionLine[] GetCollisionLines(ICollidable collisionObject)
        {
            List<CollisionLine> lines = new List<CollisionLine>();
            for (int i = 0; i <= collisionObject.Boundary.Length - 2; i++)
            {
                lines.Add(new CollisionLine(collisionObject.Boundary[i], collisionObject.Boundary[i + 1]));
            }
            lines.Add(new CollisionLine(collisionObject.Boundary[collisionObject.Boundary.Length - 1], collisionObject.Boundary[0]));
            return lines.ToArray();
        }

        /// <summary>
        /// Generates an array of Coordinates representing a circle with the given properties
        /// </summary>
        /// <param name="center">The renter of the circle</param>
        /// <param name="radius">The radius of the circle</param>
        /// <param name="secments">The amount of secments, minimum is 3</param>
        /// <returns>An array of Coordinates representing a circle</returns>
        public static Coordinate[] GetCircle(Coordinate center, double radius, int secments)
        {
            secments = secments < 3 ? 3 : secments;
            List<Coordinate> coords = new List<Coordinate>();
            for (double d = 0; d < System.Math.PI * 2; d += System.Math.PI * 2 / secments)
            {
                double y = System.Math.Sin(d) * radius + center.Y;
                double x = System.Math.Cos(d) * radius + center.X;
                coords.Add(new Coordinate(x, y));
            }
            return coords.ToArray();
        }

        /// <summary>
        /// Generates an array of Coordinates representing a rectable with the given properties
        /// </summary>
        /// <param name="topleft">The coordinate of the topleft corner</param>
        /// <param name="bottomright">The coordinate of the bottomright corner</param>
        /// <returns>An array of Coordinates representing a rectangle</returns>
        public static Coordinate[] GetRectangle(Coordinate topleft, Coordinate bottomright)
        {
            List<Coordinate> coords = new List<Coordinate>();

            coords.Add(topleft);
            coords.Add(new Coordinate(bottomright.X, topleft.Y));
            coords.Add(bottomright);
            coords.Add(new Coordinate(topleft.X, bottomright.Y));

            return coords.ToArray();
        }

    }

    public class Collision
    {
        /// <summary>
        /// Gets an array of the coordinates of the collsion
        /// </summary>
        public Coordinate[] Coordinates { get; private set; }

        /// <summary>
        /// Gets the other ICollidable object which is involved in the collision
        /// </summary>
        public ICollidable Involved { get; private set; }

        internal Collision(Coordinate[] coords, ICollidable involved)
        {
            Coordinates = coords;
            Involved = involved;
        }
    }

    public interface ICollidable
    {
        /// <summary>
        /// Gets the boundary of the ICollidable object
        /// </summary>
        Coordinate[] Boundary { get; }
        Collision[] Collisions { set; }
    }

    internal class CollisionLine
    {
        internal Coordinate P1 { get; private set; }
        internal Coordinate P2 { get; private set; }

        internal CollisionLine(Coordinate p1, Coordinate p2)
        {
            if (p1.X > p2.X)
            {
                P1 = p2;
                P2 = p1;
            }
            else if (p1.X < p2.X)
            {
                P1 = p1;
                P2 = p2;
            }
            else
            {
                P1 = p1;
                P2 = new Coordinate(p2.X + 0.0001, p2.Y);
            }
        }

        internal Coordinate IntersectsWidth(CollisionLine cl)
        {
            double x1 = P1.X, x2 = P2.X, clx1 = cl.P1.X, clx2 = cl.P2.X, y1 = P1.Y, y2 = P2.Y, cly1 = cl.P1.Y, cly2 = cl.P2.Y;

            if (x1 == x2)
                x2 += 0.0001;
            if (clx1 == clx2)
                clx2 += 0.0001;

            double m1 = 0.0, m2 = 0.0;
            double t1 = 0.0, t2 = 0.0;

            //Steigung
            m1 = (y2 - y1 * 1.0) / (x2 - x1 * 1.0);
            m2 = (cly2 - cly1 * 1.0) / (clx2 - clx1 * 1.0);

            //Parralel-Indentisch
            if (m1 == m2)
            {
                if (x1 != clx1 && x1 != clx2)
                    return Coordinate.Infinity;
                else
                    return null;
            }

            t1 = -x1 * m1 + y1;
            t2 = -clx1 * m2 + cly1;

            double xs = (t2 - t1) / (m1 - m2);
            double ys = xs * m1 + t1;

            if (xs >= x1 && xs > clx1 && xs < x2 && xs < clx2)
            {
                return new Coordinate((int)xs, (int)ys);
            }
            else
            {
                return null;
            }
        }

    }

}
